package utils.io;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import utils.binary.impl.BytesSlice;
import utils.binary.io.BytesInputStream;
import utils.binary.io.BytesOutputBuffer;
import utils.exception.RuntimeIOException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * <h1>数值掩码</h1>
 * <p>以更少的字节空间输出整数的字节数组</p>
 * <p>该类定义了使用有限字节标识一个特定范围的正整数的格式</p>
 * <p>用于表示数值的字节长度是动态的，具体根据数值的范围而定</p>
 * <p>这个特点使得 NumberMask 适用于表示小数据片的头部尺寸，尤其当一个数据块是由大量表示不同属性的小数据片构成时，使用 NumberMask 可以得到更为紧凑的字节流</p>
 * <p><b>注：</b>NumberMask 处理的数值范围处于 64 位整数（long）的范围正整数，有效数值范围位 0 ~ 2^61 </p>
 * <p><b>目前不支持负数</b></p>
 *
 * @author Zhou Yifan
 */
public enum NumberMask {

    /**
     * 极小数值范围的掩码，掩码位个数为 0（没有掩码），数据占用 1 字节，表示的数值小于 256，即 2^8
     */
    TINY((byte) 0),

    /**
     * <p>小数值范围掩码，掩码个数为1，数据最多占 2 字节</p>
     * <p>记录字节数据大小的字节编码输出长度的字节数是动态的，根据数据大小而定，最少 1 字节，最大 2 字节</p>
     * <p>使用首个字节的最高 1 位作为标识位指示头部的长度</p>
     * <ul>
     *     <li>当数值小于 128 (2^7)，则字节编码输出长度为 1 个字节，最高位标识为 0 (0b0)</li>
     *     <li>当数值小于 32768 (2^15, 32KB)，则字节编码输出长度为 2 个字节，最高位标识为 1 (0b1)</li>
     * </ul>
     */
    SHORT((byte) 1),

    /**
     * <p>正常数值范围掩码，掩码个数为2，数据最多占用 4 字节</p>
     * <p>记录字节数据大小的字节编码输出长度的字节数是动态的，根据数据大小而定，最少 1 字节，最大 4 字节</p>
     * <p>使用首个字母的最高 2 位作为标识位只是头的长度</p>
     * <ul>
     *     <li>当数值小于 64 (2^6)，则字节编码输出长度为 1 字节，最高位标识符为 0 (0b00)</li>
     *     <li>当数值小于 16384 (2^14, 16KB)，则字节编码输出长度为 2 个字节，最高位标识为 1 (0b01)</li>
     *     <li>当数值小于 4194304 (2^22, 4MB)，则字字节编码输出长度为 3 个字节，最高位标识符为 2 (0b10)</li>
     *     <li>当数值小于 1073741824 (2^30, 1GB)，则字节编码输出长度为 4 个字节，最高位标识符为 3 (0b11)</li>
     * </ul>
     */
    NORMAL((byte) 2),

    /**
     * <p>长数值范围掩码，掩码个数为3，数据最多占用 8 字节</p>
     * <p>记录字节数据大小的字节编码输出长度为的字节数是动态的，根据数据的大小而定，最少 1 个字节，最大 8 个字节</p>
     * <p>使用首个字节的最高 3 位作为标识位指示头部的长度</p>
     * <ul>
     *     <li>当数值小于 32 (2^5)，则字节编码输出长度为 1 个字节，最高位标识为 0 (0b000)</li>
     *     <li>当数值小于 8192 (2^13)，则字节编码输出长度为 2 个字节，最高位标识为 1 (0b001)</li>
     *     <li>当数值小于 2097152 (2^21)，则字节编码输出长度为 3 个字节，最高位标识为 2 (0b010)</li>
     *     <li>当数值小于 536870912 (2^29)，则字节编码输出长度为 4 个字节，最高位标识为 3 (0b011)</li>
     *     <li>当数值小于 137438953472 (2^37)，则字节编码输出长度为 5 个字节，最高位标识为 4 (0b100)</li>
     *     <li>当数值小于 35184372088832 (2^45)，则字节编码输出长度为 6 个字节，最高位标识为 5 (0b101)</li>
     *     <li>当数值小于 9007199254740992 (2^53)，则字节编码输出长度为 7 个字节，最高位标识为 6 (0b110)</li>
     *     <li>当数值小于 2305843009213693952 (2^61)，则字节编码输出长度为 8 个字节，最高位标识为 7 (0b111)</li>
     * </ul>
     */
    LONG((byte) 3);

    /**
     * <h2>掩码位的个数</h2>
     * <p>指掩码的比特数，此处设置最大为 3 比特</p>
     * <p>选取数据类型为 byte，是为了便于后续操作</p>
     * <p>个数为 C 的掩码，可以表示 2^C 种数据，这个思想很重要，使用掩码将数据分类靠的就是这样的思想</p>
     * <p>而表示的 2^C 种数据，分别代表了 1, 2, 3, ..., 2^C 字节长度的数据</p>
     * <p>此处最多支持 8 字节的，所以 C 最大值为 3</p>
     */
    public final byte BIT_COUNT;

    /**
     * <h2>头部长度的最大值</h2>
     * <p>掩码头部可以表示的数据最大值</p>
     * <p>具体为 1 << bitCount = 2^(bitCount)</p>
     * <p>其中的 bitCount 指的是掩码位的个数，即 BIT_COUNT</p>
     * <p>具体的赋值在构造函数中</p>
     * <p>如果想知道为什么最大值是 1<< bitCount，可以参考 {@link #MAX_BOUNDARY_SIZE} 的注释</p>
     * <p>由于 BIT_COUNT 最大为 3，所以 MAX_HEADER_LENGTH 最大为 1 << 3 = 0b1000 = 8</p>
     * <p>表示的具体含义就是最多允许数据总长度为 8 字节</p>
     */
    public final int MAX_HEADER_LENGTH;

    /**
     * <h2>最大边界值</h2>
     * <p>所谓的边界值，指的是不同字节的数字所能够表示的最大边界范围</p>
     * <p>例如 1 字节的数字，1111 1111，即 2^8 - 1，我们定义最大边界为 2^8，也就是说可以表示的值范围为 [0,2^8)</p>
     * <p>不过这里引入了掩码，所以 1 字节能够表示的最大值是 2^(8 - BIT_COUNT)</p>
     * <p>即如果最高位的三个比特作为掩码，那么能够表示的最大值是 1 1111，即 2^5</p>
     * <p>此外，我们还需要注意的是，2^8=1 0000 0000，也就是说 N 位数据 X 可以表示的最大值是 1L << N</p>
     * <p>放到我们这里就是，考虑掩码，N 位数据 X 可以表示的最大值是 1L << (N - BIT_COUNT)</p>
     * <p>换算一下，M 字节的数据 X 可以表示的最大值就是 1L << (M * 8 - BIT_COUNT)</p>
     */
    public final long MAX_BOUNDARY_SIZE;

    /* 该部分常数对于 TINY SHORT NORMAL 有效 */
    /* BOUNDARY_SIZE_X 表示的含义是，在 BIT_COUNT 的前提下，X 字节能够表示的最大值 */

    public final long BOUNDARY_SIZE_0;
    public final long BOUNDARY_SIZE_1;
    public final long BOUNDARY_SIZE_2;
    public final long BOUNDARY_SIZE_3;
    public final long BOUNDARY_SIZE_4;
    public final long BOUNDARY_SIZE_5;
    public final long BOUNDARY_SIZE_6;
    public final long BOUNDARY_SIZE_7;

    /**
     * <h2>边界值数组</h2>
     * <p>这个数组的长度为 MAX_HEADER_LENGTH（最大为 8）</p>
     * <p>这个数组用来存储 BIT_COUNT 条件下，N（最大为 8） 字节边界值的取值</p>
     *
     * <pre>
     * boundarySizes[0] = 1L << (08 - BIT_COUNT)
     * boundarySizes[1] = 1L << (16 - BIT_COUNT)
     * boundarySizes[2] = 1L << (24 - BIT_COUNT)
     * boundarySizes[3] = 1L << (32 - BIT_COUNT)
     * boundarySizes[4] = 1L << (40 - BIT_COUNT)
     * boundarySizes[5] = 1L << (48 - BIT_COUNT)
     * boundarySizes[6] = 1L << (56 - BIT_COUNT)
     * boundarySizes[7] = 1L << (64 - BIT_COUNT)
     * </pre>
     */
    private final long[] boundarySizes;

    /**
     * 构造函数
     *
     * @param bitCount 掩码位数，取值为 0,1,2,3，如果超出范围，将抛出 {@link IllegalArgumentException} 异常
     */
    NumberMask(byte bitCount) {
        this.BIT_COUNT = bitCount;
        this.MAX_HEADER_LENGTH = 1 << bitCount;
        this.boundarySizes = new long[MAX_HEADER_LENGTH];
        // i = 0, 1, 2, ..., MAX_HEADER_LENGTH
        for (byte i = 0; i < MAX_HEADER_LENGTH; i++) {
            boundarySizes[i] = computeBoundarySize((byte) (i + 1));
        }
        this.MAX_BOUNDARY_SIZE = boundarySizes[MAX_HEADER_LENGTH - 1];
        switch (bitCount) {
            case 0:
                // TINY，1字节
                BOUNDARY_SIZE_0 = boundarySizes[0]; // boundarySizes[0] = 1L << 8 = 2^8 = 256
                BOUNDARY_SIZE_1 = -1;
                BOUNDARY_SIZE_2 = -1;
                BOUNDARY_SIZE_3 = -1;
                BOUNDARY_SIZE_4 = -1;
                BOUNDARY_SIZE_5 = -1;
                BOUNDARY_SIZE_6 = -1;
                BOUNDARY_SIZE_7 = -1;
                break;
            case 1:
                // SHORT，2字节
                BOUNDARY_SIZE_0 = boundarySizes[0]; // boundarySizes[0] = 1L << 7 = 2^7 = 128
                BOUNDARY_SIZE_1 = boundarySizes[1]; // boundarySizes[1] = 1L << 15 = 2^15 = 32768
                BOUNDARY_SIZE_2 = -1;
                BOUNDARY_SIZE_3 = -1;
                BOUNDARY_SIZE_4 = -1;
                BOUNDARY_SIZE_5 = -1;
                BOUNDARY_SIZE_6 = -1;
                BOUNDARY_SIZE_7 = -1;
                break;
            case 2:
                // NORMAL，4字节
                BOUNDARY_SIZE_0 = boundarySizes[0]; // boundarySizes[0] = 1L << 6 = 2^6 = 64
                BOUNDARY_SIZE_1 = boundarySizes[1]; // boundarySizes[1] = 1L << 14 = 2^14 = 16384
                BOUNDARY_SIZE_2 = boundarySizes[2]; // boundarySizes[2] = 1L << 22 = 2^22 = 4194304
                BOUNDARY_SIZE_3 = boundarySizes[3]; // boundarySizes[3] = 1L << 30 = 2^30 = 1073741824
                BOUNDARY_SIZE_4 = -1;
                BOUNDARY_SIZE_5 = -1;
                BOUNDARY_SIZE_6 = -1;
                BOUNDARY_SIZE_7 = -1;
                break;
            case 3:
                // LONG，8字节
                BOUNDARY_SIZE_0 = boundarySizes[0]; // boundarySizes[0] = 1L << 5 = 2^5 = 32
                BOUNDARY_SIZE_1 = boundarySizes[1]; // boundarySizes[1] = 1L << 13 = 2^13 = 8192
                BOUNDARY_SIZE_2 = boundarySizes[2]; // boundarySizes[2] = 1L << 21 = 2^21 = 2097152
                BOUNDARY_SIZE_3 = boundarySizes[3]; // boundarySizes[3] = 1L << 29 = 2^29 = 536870912
                BOUNDARY_SIZE_4 = boundarySizes[4]; // boundarySizes[4] = 1L << 37 = 2^37 = 137438953472
                BOUNDARY_SIZE_5 = boundarySizes[5]; // boundarySizes[5] = 1L << 45 = 2^45 = 35184372088832
                BOUNDARY_SIZE_6 = boundarySizes[6]; // boundarySizes[6] = 1L << 53 = 2^53 = 9007199254740992
                BOUNDARY_SIZE_7 = boundarySizes[7]; // boundarySizes[7] = 1L << 61 = 2^61 = 2305843009213693952
                break;
            default:
                throw new IllegalArgumentException(
                        "Illegal bitCount! --[bitCount = "
                                + bitCount
                                + "], bitCount value is limited to 0,1,2,3");
        }
    }

    /**
     * 计算边界值
     *
     * @param headerLength 头部长度，单位：字节
     * @return 边界值大小
     */
    private long computeBoundarySize(int headerLength) {
        // 1L (bit) = 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0001
        // 1L (byte) = 00 00 00 00 00 00 00 01
        // byte = 8 bit，所以 headerLength 要乘 8，因为位运算是基于 bit 运算的，至于减去 BIT_COUNT，是因为要除去掩码的长度
        // Example: 1L << 16 = 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0000 0000 | 1000 0000 | 0000 0000
        return 1L << (headerLength * 8 - BIT_COUNT);
    }

    /**
     * 在指定头部长度的前提下，能够标识的数据大小的临界值（不含）
     *
     * @param headerLength 取值范围必须大于 0，小于 {@link #MAX_HEADER_LENGTH}
     * @return 临界值（不含）
     */
    public long getBoundarySize(int headerLength) {
        return boundarySizes[headerLength];
    }

    /**
     * 获取能够表示指定的数值的掩码长度，即掩码所需要的字节数
     *
     * @param number 要表示的数值，如果超出有效范围，将抛出 {@link IllegalArgumentException} 异常
     * @return 字节数，可能的返回值为 1, 2, 3, 4, 5, 6, 7, 8
     */
    public int getMaskLength(long number) {
        if (number > -1) {
            if (number < BOUNDARY_SIZE_0) {
                return 1;
            }
            if (number < BOUNDARY_SIZE_1) {
                return 2;
            }
            if (number < BOUNDARY_SIZE_2) {
                return 3;
            }
            if (number < BOUNDARY_SIZE_3) {
                return 4;
            }
            if (number < BOUNDARY_SIZE_4) {
                return 5;
            }
            if (number < BOUNDARY_SIZE_5) {
                return 6;
            }
            if (number < BOUNDARY_SIZE_6) {
                return 7;
            }
            if (number < BOUNDARY_SIZE_7) {
                return 8;
            }
        }
        throw new IllegalArgumentException(
                "Number is out of the illegal range! --[number = "
                        + number
                        + "], the number range is [0, 2^61] | 2^61 = 2305843009213693952");
    }

    /**
     * 根据目标数字生成含有掩码的字节数组
     *
     * @param number 目标数字，有效范围为 0 ~ 2^61，超出范围将造成信息丢失
     * @return 字节数组
     */
    public byte @NotNull [] generateMask(long number) {
        // 先计算掩码需要占用的字节长度
        int maskLength = getMaskLength(number);
        // 根据计算出来的长度 new 一个字节数组
        byte[] maskBytes = new byte[maskLength];
        // 用该数组接收数字
        writeMask(number, maskLength, maskBytes, 0);
        return maskBytes;
    }

    /**
     * 将指定数值，从指定位置开始写入指定字节数组
     *
     * @param number 指定数值
     * @param buffer 指定字节数组
     * @param offset 指定位置，即从何处开始写入
     * @return 写入的字节数
     */
    public int writeMask(long number, byte[] buffer, int offset) {
        // 计算掩码占用的字节长度
        int maskLength = getMaskLength(number);
        return writeMask(number, maskLength, buffer, offset);
    }

    /**
     * 将指定数值按照指定的掩码长度，从指定位置开始写入指定的字节数组
     *
     * @param number     需要写入的数据
     * @param maskLength 掩码长度，单位：字节
     * @param buffer     缓冲字节数组（目标字节数组）
     * @param offset     偏移位置，指从 buffer 何处开始写
     * @return 写入数据的长度
     */
    private int writeMask(long number, int maskLength, byte[] buffer, int offset) {
        // 计算掩码占用的字节长度
        for (int i = maskLength; i > 0; i--) {
            // 0xFF = 1111 1111，此处 A &0xFF 作用是取得最低位字节（因为也可能是取得最高位字节）
            // 这里作用是从最低位字节开始，依次将其倒序写入 buffer 字节数组中，以 maskLength = 8 为例（掩码长度最长为 8 字节），设 offset = 0
            // 设 number = 0xFFEEDDCCBBAA9988
            // buffer[7] = (number >>> 00) & 0xFF = 88
            // buffer[6] = (number >>> 08) & 0xFF = 99
            // buffer[5] = (number >>> 16) & 0xFF = AA
            // buffer[4] = (number >>> 24) & 0xFF = BB
            // buffer[3] = (number >>> 32) & 0xFF = CC
            // buffer[2] = (number >>> 40) & 0xFF = DD
            // buffer[1] = (number >>> 48) & 0xFF = EE
            // buffer[0] = (number >>> 58) & 0xFF = FF
            buffer[offset + i - 1] = (byte) ((number >>> (8 * (maskLength - i))) & 0xFF);
        }
        // 计算头字节的标识位
        // 例如，maskLength = 8, BIT_LENGTH = 3
        // indicator = 7 << 5 = 0111 << 5 = 1110 0000 = 0xE0
        // 其实这里也就说明了，indicator 最多高 3 位为 1，这也就为下面的 indicatorByte | buffer[0] 提供了支持
        byte indicatorByte = (byte) ((maskLength - 1) << (8 - BIT_COUNT));
        // 设置标识位
        // buffer[0] = 1110 0000 | 1111 1111 = 1111 1111
        // 实际上就是将高 3 位设置为掩码，因为 1|0 = 1, 0|0 = 0，所以只有前三位有效（也就是掩码了）
        buffer[offset] = (byte) (indicatorByte | buffer[offset]);
        return maskLength;
    }

    /**
     * 生成指定数值的掩码，并写入到指定的输出流
     *
     * @param number       指定数值的掩码
     * @param outputStream 指定的输出流
     * @return 写入的字节数
     */
    public int writeMask(long number, @NotNull OutputStream outputStream) {
        // 生成数据尺寸掩码
        byte[] maskBytes = generateMask(number);

        try {
            outputStream.write(maskBytes);
            return maskBytes.length;
        } catch (IOException e) {
            throw new RuntimeIOException(e.getMessage(), e);
        }
    }

    /**
     * 生成指定数值的掩码并写入到指定的输出流；
     *
     * @param number 指定数值的掩码
     * @param out    指定的缓存
     * @return 写入的字节数；
     */
    public int writeMask(long number, @NotNull BytesOutputBuffer out) {
        // 生成数据尺寸掩码；
        byte[] maskBytes = generateMask(number);

        out.write(maskBytes);
        return maskBytes.length;
    }

    /**
     * <p>解析掩码头字节获得掩码实例的完整长度</p>
     * <p><b>注：</b>在字节流中，对首字节解析获取该值后减 1，可以得到掩码后续要读取的字节长度</p>
     *
     * @param headByte 掩码头字节，即掩码字节序列的首个字节，其中包含了掩码长度信息
     * @return 掩码实例的完整长度
     */
    public int resolveMaskLength(byte headByte) {
        // 取得 headByte 最低位，然后右移 8 - BIT_COUNT，最后数值 + 1
        // 例如 headByte = 1110 0000，BIT_COUNT = 3
        // length = (1110 0000 >>> 5) + 1= 111 + 1 = 8
        int length = ((headByte & 0xFF) >>> (8 - BIT_COUNT)) + 1;
        if (length > MAX_HEADER_LENGTH) {
            throw new IllegalArgumentException(
                    "Illegal length ["
                            + length
                            + "] was resolved from the head byte of NumberMask! The length should be within range [1,"
                            + MAX_HEADER_LENGTH + "]"
            );
        }
        return length;
    }

    public long resolveMaskedNumber(byte[] markBytes) {
        return resolveMaskedNumber(markBytes, 0);
    }

    /**
     * 从字节中解析掩码表示的数值
     *
     * @param maskBytes 掩码字节数组
     * @param offset    从何处开始解析
     * @return long 值
     */
    @Contract(pure = true)
    public long resolveMaskedNumber(byte @NotNull [] maskBytes, int offset) {
        // 根据掩码标识获取掩码实例长度
        int maskLength = resolveMaskLength(maskBytes[offset]);
        // 清除首字节标识位
        // 例如 BIT_COUNT = 3，0xFF >>> 3 = 0001 1111，这样可以达到清除效果
        // 此外，这里很多时候都用了 0xFF，看似多此一举，实际上是为了防止其类型被判定为int
        // 因为这里要时刻保持操作类型是比特或者字节，除非在赋值的时候确定具体的类型
        byte numberHead = (byte) ((maskBytes[offset] & 0xFF) & (0xFF >>> BIT_COUNT));
        // 将 numberHead 转换字节大小，也就是转换成十进制值（这里是 long 值）
        // number = 0001 1111 = 0x1F = 31
        long number = numberHead & 0xFF;
        for (int i = 1; i < maskLength; i++) {
            // 把每个字节依次解析出来，若 maskLength = 8
            // 此处 &0xFF 是取最低 8 位，也就是最低位字节（不过 maskBytes 是字节类型数组，实际上也就是取得其字节码）
            number = (number << 8) | (maskBytes[offset + i] & 0xFF);
        }
        return number;
    }

    /**
     * 从字节中解析掩码表示的数值；
     *
     * @param bytes {@link BytesSlice}
     * @return int
     */
    public long resolveMaskedNumber(BytesSlice bytes) {
        return resolveMaskedNumber(bytes, 0);
    }

    /**
     * 从字节中解析掩码表示的数值；
     *
     * @param bytes  {@link BytesSlice}
     * @param offset 起始位置
     * @return int
     */
    public long resolveMaskedNumber(@NotNull BytesSlice bytes, int offset) {
        byte headByte = bytes.getByte(offset);
        int maskLen = resolveMaskLength(headByte);

        // 清除首字节的标识位；
        byte numberHead = (byte) ((headByte & 0xFF) & (0xFF >>> BIT_COUNT));

        // 转换字节大小；
        long number = numberHead & 0xFF;
        for (int i = 1; i < maskLen; i++) {
            number = (number << 8) | (bytes.getByte(offset + i) & 0xFF);
        }

        return number;
    }

    /**
     * 从字节中解析掩码表示的数值；
     *
     * @param bytesStream {@link BytesInputStream}
     * @return int
     */
    public long resolveMaskedNumber(@NotNull BytesInputStream bytesStream) {
        byte headByte = bytesStream.readByte();
        int maskLen = resolveMaskLength(headByte);

        // 清除首字节的标识位；
        byte numberHead = (byte) ((headByte & 0xFF) & (0xFF >>> BIT_COUNT));

        // 转换字节大小；
        long number = numberHead & 0xFF;
        for (int i = 1; i < maskLen; i++) {
            number = (number << 8) | (bytesStream.readByte() & 0xFF);
        }

        return number;
    }

    /**
     * 从字节流中解析掩码表示的数值
     *
     * @param inputStream 字节流（输入流）
     * @return 掩码表示的数值
     */
    public long resolveMaskedNumber(@NotNull InputStream inputStream) {
        try {
            byte[] buff = new byte[MAX_HEADER_LENGTH];
            // 解析头字节，从 0 位置开始，读取 1 个字节
            int length = inputStream.read(buff, 0, 1);
            if (length < 1) {
                throw new IllegalArgumentException(
                        "No enough bytes for the size header's indicator byte! --[length = "
                                + length + "]"
                );
            }
            int maskLength = resolveMaskLength(buff[0]);
            if (maskLength > 1) {
                inputStream.read(buff, 1, maskLength - 1);
            }
            return resolveMaskedNumber(buff, 0);
        } catch (IOException e) {
            throw new RuntimeIOException(e.getMessage(), e);
        }
    }
}
